﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Xenta.Utils
{
    /// <summary>
    /// Contains extension methods.
    /// </summary>
    public static class Extensions
    {
        #region String

        /// <summary>
        /// Parses the string into specified type.
        /// </summary>
        /// <param name="s">The input string.</param>
        /// <param name="t">The target type to convert.</param>
        /// <param name="defaultValue">The default value to return if convertation fails.</param>
        /// <returns>The parsed value.</returns>
        public static object ParseAs(this string s, Type t, object defaultValue = null)
        {
            return StringHelper.ParseAs(s, t, defaultValue);
        }

        /// <summary>
        /// Parses the input string into specified type.
        /// </summary>
        /// <typeparam name="T">The target type to convert.</typeparam>
        /// <param name="s">The input string.</param>
        /// <param name="defaultValue">The default value to return if convertation fails.</param>
        /// <returns>The parsed value on success; otherwise returns the default value.</returns>
        public static T ParseAs<T>(this string s, T defaultValue = default(T))
        {
            return StringHelper.ParseAs(s, defaultValue);
        }

        /// <summary>
        /// Concatenates all the elements of a string array, using the specified separator
        /// between each element.
        /// </summary>
        /// <param name="separator">The string to use as a separator.</param>
        /// <param name="stringArray">An array that contains the elements to concatenate.</param>
        /// <param name="trailingSeparator">A value indicates whether a trailing separator is required.</param>
        /// <returns>A string that consists of the elements in value delimited by the separator string.</returns>
        public static string Merge(this IEnumerable<String> stringArray, 
            string separator, 
            bool trailingSeparator = false)
        {
            return StringHelper.Merge(stringArray, separator, trailingSeparator);
        }

        /// <summary>
        /// Receives a string and returns the string with its letters reversed.
        /// </summary>
        /// <param name="s">The input string.</param>
        /// <returns>The reversed string.</returns>
        public static string Reverse(this string s)
        {
            return StringHelper.Reverse(s);
        }

        /// <summary>
        /// Trims the input string to required length.
        /// </summary>
        /// <param name="s">The input string.</param>
        /// <param name="length">The required lenght.</param>
        /// <param name="applyStringTrim">A value indicates whether standard String.Trim can be applied.</param>
        /// <returns>The trimmed string.</returns>
        public static string TrimToLen(this string s, int length, bool applyStringTrim = true)
        {
            return StringHelper.TrimToLength(s, length);
        }

        /// <summary>
        /// Determines if the input string represents global unique identifier.
        /// </summary>
        /// <param name="s">The input string.</param>
        /// <returns>True if the string represents global unique identifier; otherwise false.</returns>
        public static bool IsGuid(this string s)
        {
            return StringHelper.IsGuid(s);
        }

        /// <summary>
        /// Determines if the input string represents email.
        /// </summary>
        /// <param name="s">The input string.</param>
        /// <returns>True if the string represents email; otherwise false.</returns>
        public static bool IsEmail(this string s)
        {
            return StringHelper.IsEmail(s);
        }

        /// <summary>
        /// Checks, if an input string matches the required pattern.
        /// </summary>
        /// <param name="s">The input string.</param>
        /// <param name="pattern">The pattern.</param>
        /// <returns>true it matches; otherwise false.</returns>
        public static bool IsMatch(this string s, string pattern)
        {
            return StringHelper.IsMatch(s, pattern);
        }

        /// <summary>
        /// Checks, if an input string matches the required pattern.
        /// </summary>
        /// <param name="s">The input string.</param>
        /// <param name="pattern">The pattern.</param>
        /// <returns>true it matches; otherwise false.</returns>
        public static bool IsMatch(this string s, Regex pattern)
        {
            return StringHelper.IsMatch(s, pattern);
        }

        /// <summary>
        /// Converts the specified string to titlecase.
        /// </summary>
        /// <param name="s">The input string.</param>
        /// <returns>The converted string.</returns>
        public static string Capitalize(this string s)
        {
            return StringHelper.Capitalize(s);
        }

        #endregion

        #region Type

        /// <summary>
        /// Determines whethere the type implements an interface.
        /// </summary>
        /// <typeparam name="TInterface">The interface type.</typeparam>
        /// <param name="type">The type.</param>
        /// <returns>true if interface is implemented by type; otherwise false.</returns>
        public static bool DoesImplementInterface<TInterface>(this Type type)
        {
            return ReflectionUtility.DoesImplementInterface<TInterface>(type);
        }

        /// <summary>
        /// Lists interfaces of type filtered using fiter.
        /// </summary>
        /// <param name="filter">The filter.</param>
        /// <param name="type">The type.</param>
        /// <returns>The enumeration of interface types.</returns>
        public static IEnumerable<Type> ListInterfaces(this Type type, Predicate<Type> filter)
        {
            return ReflectionUtility.Interfaces(type, filter);
        }

        #endregion

        #region NameValueCollection

        /// <summary>
        /// Parses an input string as name-value entries.
        /// </summary>
        /// <param name="nvc">The name-value collection.</param>
        /// <param name="s">The input string.</param>
        /// <returns>The modified NameValueCollection.</returns>
        /// <remarks>
        /// An input string should be in following format:
        /// name_1=val_1;name_2=val_2;...;name_n=val_n;
        /// </remarks>
        public static NameValueCollection Load(this NameValueCollection nvc, string s)
        {
            if(String.IsNullOrWhiteSpace(s))
                return nvc;
            var kv = s.Split(new[]
            {
                ';'
            }, StringSplitOptions.RemoveEmptyEntries);
            if(!kv.Any())
                return nvc;
            var tmp = kv.Select(x => x.Split('='))
                .Where(x => x.Length == 2);
            foreach(var i in tmp)
                nvc.Add(i[0], i[1]);
            return nvc;
        }

        /// <summary>
        /// Unloads a collection of name-value enties to an output string.
        /// </summary>
        /// <param name="nvc">The name-value to save.</param>
        /// <returns>The output string.</returns>
        /// <remarks>
        /// An output string will be in following format:
        /// name_1=val_1;name_2=val_2;...;name_n=val_n;
        /// </remarks>
        public static string Unload(this NameValueCollection nvc)
        {
            if(nvc.AllKeys.Any())
                return String.Empty;
            var sb = new StringBuilder();
            foreach(var i in nvc.AllKeys)
                sb.AppendFormat("{0}={1};", i, nvc[i]);
            return sb.ToString();
        }

        #endregion

        #region IEnumerable

        /// <summary>
        /// Takes an action on each element of the sequence.
        /// </summary>
        /// <typeparam name="T">The type of elements in a sequence.</typeparam>
        /// <param name="source">The sequence of elements.</param>
        /// <param name="action">The action function.</param>
        public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
        {
            foreach(var i in source)
                action(i);
        }
        
        /// <summary>
        /// Generates a randomly shuffled copy of a sequence.
        /// </summary>
        /// <typeparam name="TItem">The type of elements in a sequence.</typeparam>
        /// <param name="source">The source sequence.</param>
        /// <returns>The randomly shuffled copy of a sequence.</returns>
        public static IEnumerable<TItem> Shuffle<TItem>(this IEnumerable<TItem> source)
        {
            var rng = new Random();
            var tmp = source.ToArray();

            for(int i = tmp.Length - 1; i >= 0; i--)
            {
                int n = rng.Next(i + 1);

                yield return tmp[n];

                tmp[n] = tmp[i];
            }
        }

        /// <summary>
        /// Converts the sequence of TIn elements to a sequence of TOut elements.
        /// </summary>
        /// <typeparam name="TIn">The type of item in an input sequence.</typeparam>
        /// <typeparam name="TOut">The type of item in an output sequence.</typeparam>
        /// <param name="source">The sequence of elements to convert.</param>
        /// <param name="mapper">The mapper function.</param>
        /// <returns>The sequence of TOut elements.</returns>
        public static IEnumerable<TOut> Map<TIn, TOut>(this IEnumerable<TIn> source, Func<TIn, TOut> mapper)
        {
            return source.Select(mapper);
        }

        #endregion

        #region IDicitionary

        /// <summary>
        /// Fills the dictionary with key-value pairs.
        /// </summary>
        /// <param name="dst">The dictionary.</param>
        /// <param name="src">The object.</param>
        /// <typeparam name="TValue">The type of value.</typeparam>
        public static void AddValues<TValue>(this IDictionary<String, TValue> dst, object src)
        {
            if(src == null) 
                return;
            foreach(PropertyDescriptor i in TypeDescriptor.GetProperties(src))
                dst[i.Name] = (TValue)i.GetValue(src);
        }

        /// <summary>
        /// Fills the dictionary with key-value pairs.
        /// </summary>
        /// <param name="dst">The destination dictionary.</param>
        /// <param name="src">The source dictionary.</param>
        /// <typeparam name="TKey">The type of value.</typeparam>
        /// <typeparam name="TValue">The type of value.</typeparam>
        public static void AddValues<TKey, TValue>(this IDictionary<TKey, TValue> dst, 
            IEnumerable<KeyValuePair<TKey, TValue>> src)
        {
            if(src == null)
                return;
            foreach(var kv in src)
                dst[kv.Key] = kv.Value;
        }

        /// <summary>
        /// Parses an input string as collection of dictionary enties.
        /// </summary>
        /// <param name="dic">The dictionary to fil.</param>
        /// <param name="s">The input string.</param>
        /// <returns>The modified dictionary.</returns>
        /// <remarks>
        /// An input string should be in following format:
        /// name_1=val_1;name_2=val_2;...;name_n=val_n;
        /// </remarks>
        public static IDictionary<String, String> Load(this IDictionary<String, String> dic, string s)
        {
            if(String.IsNullOrWhiteSpace(s))
                return dic;
            var kv = s.Split(new[]
            {
                ';'
            }, StringSplitOptions.RemoveEmptyEntries);
            if(!kv.Any())
                return dic;
            var tmp = kv.Select(x => x.Split('='))
                .Where(x => x.Length == 2);
            foreach(var i in tmp)
                dic[i[0]] = i[1];
            return dic;
        }

        /// <summary>
        /// Unloads a collection of dictionary enties to an output string.
        /// </summary>
        /// <param name="dic">The dictionary to save.</param>
        /// <returns>The output string.</returns>
        /// <remarks>
        /// An output string will be in following format:
        /// name_1=val_1;name_2=val_2;...;name_n=val_n;
        /// </remarks>
        public static string Unload(this IDictionary<String, String> dic)
        {
            if(!dic.Any())
                return String.Empty;
            var sb = new StringBuilder();
            foreach(var kv in dic)
                sb.AppendFormat("{0}={1};", kv.Key, kv.Value);
            return sb.ToString();
        }

        #endregion

        #region Binary

        /// <summary>
        /// Converts the array of bytes to its equivalent hex based string representation.
        /// </summary>
        /// <param name="b">An array of bytes.</param>
        /// <returns>The string representation.</returns>
        public static string ToHexString(this byte[] b)
        {
            return BinaryHelper.ToHexString(b);
        }

        /// <summary>
        /// Converts the array of bytes to its equivalent base64 based string representation.
        /// </summary>
        /// <param name="b">An array of bytes.</param>
        /// <returns>The string representation.</returns>
        public static string ToBase64String(this byte[] b)
        {
            return BinaryHelper.ToBase64String(b);
        }

        #endregion

        #region DateTime

        /// <summary>
        /// Converts the date and time value to local.
        /// </summary>
        /// <param name="dt">The date and time value.</param>
        /// <param name="utcOffset">The utc offset in minutes.</param>
        /// <returns>The converted date and time value.</returns>
        public static DateTime ToLocal(this DateTime dt, int utcOffset)
        {
            return DateHelper.Convert(dt, DateTimeKind.Local, utcOffset);
        }

        /// <summary>
        /// Converts the date and time value to UTC.
        /// </summary>
        /// <param name="dt">The date and time value.</param>
        /// <param name="utcOffset">The utc offset in minutes.</param>
        /// <returns>The converted date and time value.</returns>
        public static DateTime ToUniversal(this DateTime dt, int utcOffset)
        {
            return DateHelper.Convert(dt, DateTimeKind.Utc, utcOffset);
        }

        /// <summary>
        /// Converts the date and time value to a string representaion.
        /// </summary>
        /// <param name="dt">The date and time value.</param>
        /// <param name="format">The date and time format.</param>
        /// <returns>The string representaion.</returns>
        public static string ToStringByFmt(this DateTime dt, string format)
        {
            return DateHelper.ToString(dt, format);
        }

        #endregion
    }
}
